package com.vedantatree.redmineconnector;

import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

import com.vedantatree.redmineconnector.bdo.Issue;
import com.vedantatree.redmineconnector.bdo.Project;
import com.vedantatree.redmineconnector.bdo.User;
import com.vedantatree.redmineconnector.utils.Utilities;


/**
 * This object builds the URL for various operations performed using Redmine Restlet services. This comes in existence
 * to centralize the logic of URL building at one place, as, pattern of URL is same for all type of objects.
 * 
 * 
 * @author Mohit Gupta [mohit.gupta@vedantatree.com]
 * @since 1.1
 */
public class URLBuilder
{

	private static HashMap<Class<?>, String>	URL_MAP						= new HashMap<Class<?>, String>();
	private static String						URL_SEPARATOR				= "/";
	private static String						URL_REQUEST_POSTFIX			= ".xml";
	private static String						URL_SECRURITY_KEY_PREFIX	= "key=";
	private static String						URL_PARAM_PREFIX			= "?";
	private static String						URL_PARAM_SEPARATOR			= "&";
	private static String						URL_INCLUDE_KEY				= "include=";

	static
	{
		URL_MAP.put( Project.class, "projects" );
		URL_MAP.put( Issue.class, "issues" );
		URL_MAP.put( User.class, "users" );

		// Keep on adding other Redmine Objects and their URL mapping here, as we support these
	}

	/**
	 * Address of Server Host. It should be like http://localhost:3000
	 */
	private String								serverAddress;

	/**
	 * Security Key generated by Redmine. It is a kind of pass to access the Redmine Restlet API.
	 * 
	 * Alternative way could be to use User Id and Password. However, for now, only security key is support. Moreover,
	 * we find this approach to use security key better.
	 */
	private String								securityKey;

	// TODO later we may want to support other authentication like user id and password

	public URLBuilder( String serverAddress, String securityKey )
	{
		Utilities.assertQualifiedString( serverAddress, "Server Address" );
		Utilities.assertQualifiedString( securityKey, "Security Key" );
		this.serverAddress = serverAddress;
		this.securityKey = securityKey;
	}

	// -------------- URL formats ---------------------------------
	// Create > "/projects.xml?key=" + getSecurityKey();
	// Update > "/projects/" + projectId + ".xml?key=" + getSecurityKey();
	// Delete > "/projects/" + projectId + ".xml?key=" + getSecurityKey();
	// Get By Id > "/projects/" + projectId + ".xml?include=trackers";
	// Get Object List > new DefaultDataPaginator( ProjectsContainer.class, "/projects.xml", 0, 50 );
	// ------------------------------------------------------------

	/**
	 * This method will build the URL to create the object of specified type.
	 * 
	 * @param objectType object type for which URL needs to be built
	 * @return URL for creating the object of specified type
	 */
	// TODO how to specify RedmineBDO as param type which can support sub - classes also
	public String buildURLToCreateObject( Class<?> objectType )
	{
		String url = getBaseURLForType( objectType, null );
		return url;
	}

	/**
	 * This method builds the URL to update an object of given type
	 * 
	 * @param objectType Type of object which needs to be updated
	 * @param objectId Id of the object which needs to be updated
	 * @return URL for updating the object for given type and ID
	 */
	public String buildURLToUpdateObject( Class<?> objectType, Object objectId )
	{
		return getBaseURLForType( objectType, objectId );
	}

	/**
	 * This method builds the URL to delete an object of given type
	 * 
	 * @param objectType Type of object which needs to be deleted
	 * @param objectId Id of the object which needs to be deleted
	 * @return URL for deleting the object for given type and ID
	 */
	public String buildURLToDeleteObject( Class<?> objectType, Object objectId )
	{
		return getBaseURLForType( objectType, objectId );
	}

	/**
	 * This method builds the URL to retrieve an object from Redmine for given criteria
	 * 
	 * @param objectType Type of object to retrieve
	 * @param objectId Id of the object to Retrieve
	 * @param includes Collection of 'includes' criteria string. These type of objects should be included with retrieved
	 *        object (if applicable)
	 * @return URL for retrieving the object for given criteria
	 */
	public String buildURLToGetObjectById( Class<?> objectType, Object objectId, Collection<String> includes )
	{
		String url = getBaseURLForType( objectType, objectId );
		url = appendIncludesToURL( url, includes );
		return url;
	}

	/**
	 * This method builds the URL to retrieve a list of objects from Redmine for given criteria
	 * 
	 * @param objectType Type of objects to retrieve
	 * @param includes Collection of 'includes' criteria string. These type of objects should be included with retrieved
	 *        object (if applicable)
	 * @param filters Collections of 'filters' criteria key<>value pair. These filters should be applicable to the list
	 *        returned by Redmine.
	 * @return URL for retrieving the list of objects for given criteria
	 */
	public String buildURLToGetObjectsList( Class<?> objectType, Collection<String> includes,
			Map<String, String> filters )
	{
		String url = getBaseURLForType( objectType, null );
		url = appendFilterToURL( url, filters );
		url = appendIncludesToURL( url, includes );
		return url;
	}

	/**
	 * This method will append the filter parameter to the URL. Filters are applicable only if user is fetching list of
	 * objects.
	 * 
	 * @param url URL built by this URL Builder for current request, without includes
	 * @param filters Map of 'filter' criteria. It is having key value pair, key as filter name and value as filter
	 *        value
	 * @return An updated URL which has list of 'filter' criteria also
	 */
	private String appendFilterToURL( String url, Map<String, String> filters )
	{
		if( filters == null || filters.size() == 0 )
		{
			return url;
		}

		String filterString = "";
		for( Iterator<Map.Entry<String, String>> iterator = filters.entrySet().iterator(); iterator.hasNext(); )
		{
			filterString += URL_PARAM_SEPARATOR;
			Map.Entry<String, String> entry = (Map.Entry<String, String>) iterator.next();
			filterString += entry.getKey() + "=" + entry.getValue();
		}

		return url + filterString;
	}

	/**
	 * This method will append the includes string to the URL. 'Includes' criteria is applicable only to 'get'
	 * operation.
	 * 
	 * @param url URL built by this URL Builder for current request, without includes
	 * @param includes Collection of includes criteria
	 * @return An updated URL which has list of 'includes' criteria also
	 */
	private String appendIncludesToURL( String url, Collection<String> includes )
	{
		if( includes == null || includes.size() == 0 )
		{
			return url;
		}
		url += URL_PARAM_SEPARATOR;
		url += URL_INCLUDE_KEY;

		String includeString = "";
		for( Iterator<String> iterator = includes.iterator(); iterator.hasNext(); )
		{
			if( includeString.trim().length() != 0 )
			{
				includeString += ", ";
			}
			includeString += (String) iterator.next();
		}
		url += includeString;

		return url;
	}

	private String getBaseURLForType( Class objectType, Object objectId )
	{
		String URL = serverAddress;
		String objectURL = URL_MAP.get( objectType );
		if( !Utilities.isQualifiedString( objectURL ) )
		{
			throw new RCRuntimeException( RCRuntimeException.ILLEGAL_STATE,
					"No URL mapping found for specified object type. It could be some development bug. objectType["
							+ objectType + "]" );
		}
		URL += URL_SEPARATOR + objectURL;

		// add object id, if specified. It means, request is for update, delete or get by id
		if( objectId != null )
		{
			URL += URL_SEPARATOR + objectId;
		}

		// adding .xml
		URL += URL_REQUEST_POSTFIX;

		// adding start of parameters mark i.e. '?'
		URL += URL_PARAM_PREFIX;

		// adding security key
		URL += URL_SECRURITY_KEY_PREFIX;
		URL += securityKey;

		return URL;
	}
}
